#include <kutils.h>
#include <string.h>
#include "task.h"
#include "elf.h"
#include "display.h"
#include "memory.h"
#include "paging.h"
#include "vfs.h"
#include "keyboard.h"

u32int task_generate_id(void);
void task_general_entry(void);

task_t* current_task;
extern address_space_t* active_space;
extern address_space_t* kspace;
volatile u8int returning = 0;
volatile u8int tasking = 0;
volatile u32int next_id = 0;
volatile u32int tmp_esp, tmp_ebp = 0;
volatile u32int task_tmp_entry = 0;
s8int task_tmp_name[1024];
u32int total_cycles = 0;
u32int working_cycles = 0;
volatile u32int task_working = 0;
u32int cpu_usage = 0;

u32int task_generate_id(void)
{
	u32int id = next_id;
	next_id++;
	return id;
}

void task_general_entry(void)
{
#ifdef FLUIDIUM_DEBUG
	write_string("Task: General entry for task ");
	write_string(current_task->name);
	write_string("\n");
#endif
	
	u32int elf_entry = elf_load(current_task->name);
	if(elf_entry)
	{
#ifdef FLUIDIUM_DEBUG
		write_string("Task: elf_load successful. Entry pos32int: ");
		write_hex(elf_entry);
		write_string("\n");
#endif
		__asm__ __volatile__("jmp *%0" : : "r"(elf_entry));
	}
#ifdef FLUIDIUM_DEBUG
	write_string("Task: elf_load failed. Exiting new task.\n");
#endif
	task_exit();
}

s32int task_exec(s8int* path)
{
	return task_create(path, (u32int)&task_general_entry);
}

void task_exit(void)
{
	disable_interrupts();

	if(tmp_esp > 0x101FFF)// only non kernel stacks need to be moved temporarally
	{
		__asm__ __volatile__("mov %0, %%ESP" : : "r"(0x101FFF)); //temporary lower stack
		__asm__ __volatile__("mov %0, %%EBP" : : "r"(0x101FFF));
	}

	switch_address_space(kspace);

#ifdef FLUIDIUM_DEBUG
	write_string("Task: got exit for ");
	write_string(current_task->name);
	write_string("\n");
#endif

	s32int last = 0;
	if(current_task == current_task->next)
	{
		write_string("Task: Warning! Exiting the last task. This will hang the system.\n");
		last = 1;
	}

	task_t* prev = current_task->prev;
	task_t* next = current_task->next;
	
	prev->next = next;
	next->prev = prev;

	// clean up resources
	destroy_address_space(current_task->address_space);
	kfree(current_task->first_thread);
	kfree(current_task);

#ifdef FLUIDIUM_DEBUG
	write_string("Task: fully exited\n");
#endif

	if(last)
	{
		current_task = 0;
		while(1) __asm__ ("hlt");
	}
	else
	{
		current_task = next;

		switch_address_space(current_task->address_space);

		if(!current_task->first_thread->started)
		{	
			//this task has never been entered before,
			//meaning that it has never been preempted
			//and it's state has never been saved on the stack.
			//in this case we need to just jump right s32into the start of the task without returning.
			current_task->first_thread->started = 1;
			__asm__ __volatile__("sti");
			__asm__ __volatile__("jmp *%0" : : "r"(current_task->first_thread->entry));
		}

		//load the stack of the current task
		tmp_esp = current_task->first_thread->esp;
		tmp_ebp = current_task->first_thread->ebp;
		__asm__ __volatile__("mov %0, %%ESP" : : "r"(tmp_esp));
		__asm__ __volatile__("mov %0, %%EBP" : : "r"(tmp_ebp));

		enable_interrupts();
		return;
	}
}

// This function is very picky about the order in which things are done.
// Especially in relation to stacks and address spaces.
// BE VERY CAREFUL! :)
s32int task_create(s8int* name, u32int entry)
{
	disable_interrupts();

#ifdef FLUIDIUM_DEBUG
	write_string("Task: Creating new task ");
	write_string(name);
	write_string("\n");
#endif

	// grab items off of the current task's stack
	task_tmp_entry = entry;
	memset(task_tmp_name, 0, sizeof(s8int) * 1024);
	strcpy(task_tmp_name, name);

	// The following code needs to have a stack that is common between both address spaces.
	// A special place is reserved for this just below the Kernel (it is also the stack
	// that the kernel uses when it first boots up).
	__asm__ __volatile__("mov %%ESP, %0" : "=r"(tmp_esp)); //save esp and ebp
	__asm__ __volatile__("mov %%EBP, %0" : "=r"(tmp_ebp));
	if(tmp_esp > 0x101FFF)// only non kernel stacks need to be moved temporarally
	{
		__asm__ __volatile__("mov %0, %%ESP" : : "r"(0x101FFF)); //temporary lower stack
		__asm__ __volatile__("mov %0, %%EBP" : : "r"(0x101FFF));
	}

	s32int return_status = 0;// assume failure

	// save the current task's address space for switching back to later
	address_space_t* prev_space = active_space;

	// All new tasks must be created while in the kernel's address space
	// so that they can be accessed later no matter which task is running.
	// This is because all new address spaces are cloned directly from the 
	// kernel's address space.
	switch_address_space(kspace);

	task_t* new_task = (task_t*)kmalloc(sizeof(task_t));
	if(!new_task)
		goto exit_task_create;

	new_task->id = task_generate_id();

	new_task->name = (s8int*)kmalloc(sizeof(s8int) * 1024);
	if(!new_task->name)
		goto exit_task_create;
	strcpy(new_task->name, task_tmp_name);

	new_task->level = 10;		// medium priority
	new_task->starvation = 0;	// no starvation.
	
	new_task->stdin = open_fs("/devices/kbd");
	new_task->stdout = open_fs("/devices/txt");

	new_task->first_thread = (thread_t*)kmalloc(sizeof(thread_t));
	if(!new_task->first_thread)
		goto exit_task_create;
	new_task->first_thread->entry = task_tmp_entry;

	// this will clone the kernel space in it's current state, with all of the above allocations.
	address_space_t* new_space = create_address_space();
	if(!new_space)
		goto exit_task_create;

	new_task->address_space = new_space;

	// Must breifly switch s32into the new address space (which is a clone of the current kspace)
	// in order to allocate space for the stack (which should be unique to every task and 
	// therefore can not be allocated in the kernel's address space).
	switch_address_space(new_task->address_space);

	if(!page_alloc(new_task->address_space, 0x4FFFE000, 0x4FFFFFFF))
		goto exit_task_create;

	switch_address_space(kspace);

	new_task->first_thread->esp = 0x4FFFFFEF;// Sort of a hack. GCC insists on adding 0x10 to ESP
	new_task->first_thread->ebp = 0x4FFFFFEF;// later on in the code. This gets around that.
	new_task->first_thread->started = 0;//this thread has never been started before
	new_task->first_thread->next = 0;//no next thread in the list

	new_task->current_thread = new_task->first_thread;
	new_task->next_thread = new_task->first_thread;

	if(!current_task)
	{
		current_task = new_task;
		current_task->next = current_task;
		current_task->prev = current_task;
	}
	else
	{
		task_t* prev = current_task;
		task_t* next = current_task->next;

		prev->next = new_task;
		new_task->prev = prev;

		next->prev = new_task;
		new_task->next = next;
	}

	return_status = 1;

#ifdef FLUIDIUM_DEBUG
	write_string("Task: New task created.\n");
#endif

exit_task_create: // yes, shame on me. I used a GOTO!!! Seriously, in this case
						// it means less repeated code and better readability.

	// switch back to the previous address space
	switch_address_space(prev_space);

	//switch back to the previous stack
	__asm__ __volatile__("mov %0, %%ESP" : : "r"(tmp_esp)); //restore esp and ebp
	__asm__ __volatile__("mov %0, %%EBP" : : "r"(tmp_ebp));
	
	enable_interrupts();

	return return_status;
}

void task_switch(void)
{
disable_interrupts();
	if(tasking)
	{
		if(current_task)
		{			
			//save the current task's stack
			if(current_task->first_thread->started)
			{
				__asm__ __volatile__("mov %%ESP, %0" : "=r"(tmp_esp));
				__asm__ __volatile__("mov %%EBP, %0" : "=r"(tmp_ebp));
				current_task->first_thread->esp = tmp_esp;
				current_task->first_thread->ebp = tmp_ebp;
			}
			if(tmp_esp > 0x101FFF)// only non kernel stacks need to be moved temporarally
			{
				__asm__ __volatile__("mov %0, %%ESP" : : "r"(0x101FFF)); //temporary lower stack
				__asm__ __volatile__("mov %0, %%EBP" : : "r"(0x101FFF));
			}

#ifdef FLUIDIUM_DEBUG
//			write_string("\nTask: Switch from ");
//			write_string(current_task->name);
//			write_string(" to ");
//			write_string(current_task->next->name);
//			write_string("\n\n");
#endif		

			// Switching to task N's address space can only be acomplished from the 
			// address space in which task N was created. That is, the kernel's 
			// address space.
			switch_address_space(kspace);

			//switch the current task
			while(current_task->starvation < current_task->level)
			{
				current_task->starvation++;
				current_task = current_task->next;
			}
			current_task->starvation = 0;

			switch_address_space(current_task->address_space);

			//load the stack of the current task
			tmp_esp = current_task->first_thread->esp;
			tmp_ebp = current_task->first_thread->ebp;
			__asm__ __volatile__("mov %0, %%ESP" : : "r"(tmp_esp));
			__asm__ __volatile__("mov %0, %%EBP" : : "r"(tmp_ebp));

			total_cycles++;
			working_cycles += task_working;
			if(total_cycles >= 100)
			{
				cpu_usage = working_cycles;
				working_cycles = 0;
				total_cycles = 0;
			}
			task_working = 1;
			if(!current_task->first_thread->started)
			{	
				//this task has never been entered before,
				//meaning that it has never been preempted
				//and it's state has never been saved on the stack.
				//in this case we need to just jump right s32into the start of the task without returning.
				current_task->first_thread->started = 1;
				__asm__ __volatile__("sti");
				__asm__ __volatile__("jmp *%0" : : "r"(current_task->first_thread->entry));
			}
			//this thread has been started and preempted before.
			//this means that it's state was saved on it's stack previously.
			//so just return and it should go right back to where it was before.
			enable_interrupts();
			return;
		}
	}	

}	

// puts the current task s32into the given priority level.
s32int task_level(s8int level)
{
	disable_interrupts();
	if(level > 0 && level < 127)
	{
		current_task->level = level;
		enable_interrupts();
		return 1;
	}
	else
	{
		enable_interrupts();
		return 0;
	}
}

void init_tasking(void)
{
#ifdef FLUIDIUM_DEBUG
	write_string("Task: Init\n");
#endif
	tasking = 1;
	return;
}
